﻿using System;
using System.IO;
using System.Threading;
using Microsoft.Xna.Framework;
using NuclearWinter;
using StoCombatLogHUD.Configuration;
using StoCombatLogHUD.GameStates;
using StoCombatLogHUD.Toolkit.ExtensionMethods;
using NuclearUI = NuclearWinter.UI;
using StoCombatLogHUD.Parsing;
using StoCombatLogHUD.Statistics;
using StoCombatLogHUD.Styles;

namespace StoCombatLogHUD
{
	/// <summary>
	/// This is the main type for your game
	/// </summary>
	public class Main : NuclearGame
	{
		/// <summary>
		/// The combat log parser
		/// </summary>
		private CombatLogParser combatLogParser;

		/// <summary>
		/// Gets the combat log statistics.
		/// </summary>
		/// <value>
		/// The combat log statistics.
		/// </value>
		public CombatLogStatistics CombatLogStatistics { get; private set; }

		/// <summary>
		/// Gets the UI style.
		/// </summary>
		/// <value>
		/// The UI style.
		/// </value>
		public UIStyle UIStyle { get; private set; }

		/// <summary>
		/// Occurs when [combat log changed].
		/// </summary>
		public event EventHandler CombatLogChanged;

		/// <summary>
		/// Gets or sets the main screen.
		/// </summary>
		/// <value>
		/// The main screen.
		/// </value>
		public NuclearUI.Screen MainScreen { get; set; }

		/// <summary>
		/// Gets or sets the game state manager.
		/// </summary>
		/// <value>
		/// The game state manager.
		/// </value>
		public GameStateManager GameStateManager { get; set; }

		/// <summary>
		/// Gets or sets the configuration.
		/// </summary>
		/// <value>
		/// The configuration.
		/// </value>
		public MainConfiguration Configuration { get; set; }

		/// <summary>
		/// The target frames per second
		/// </summary>
		private const int TargetFramesPerSecond = 30;

		/// <summary>
		/// Initializes a new instance of the <see cref="Main" /> class.
		/// </summary>
		public Main()
		{
			Configuration = new MainConfiguration();
			UIStyle = new UIStyle(this);

			// Set heigth and width
			Graphics.PreferredBackBufferWidth = 640;
			Graphics.PreferredBackBufferHeight = 520;

			// Limit frames / second
			IsFixedTimeStep = true;
			TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond/TargetFramesPerSecond);
		}

		/// <summary>
		/// Allows the game to perform any initialization it needs to before starting to run.
		/// This is where it can query for any required services and load any non-graphic
		/// related content.  Calling base.Initialize will enumerate through any components
		/// and initialize them as well.
		/// </summary>
		protected override void Initialize()
		{
			base.Initialize();

			// Window settings
			Window.AllowUserResizing = false;
			IsMouseVisible = true;

			// Create the main screen
			MainScreen = new NuclearUI.Screen(this,
			                                  UIStyle,
			                                  GraphicsDevice.Viewport.Width,
			                                  GraphicsDevice.Viewport.Height)
				{
					IsActive = true
				};

			// Initialize GameStateManager and display summary screen
			GameStateManager = new GameStateManager(this);
			GameStateManager.DisplaySummary();

			// Initialize combat log instances
			InitializeCombatlog(Configuration.CombatLogPath);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources.
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			CloseCombatlog();
			base.Dispose(disposing);
		}

		/// <summary>
		/// LoadContent will be called once per game and is the place to load
		/// all of your content.
		/// </summary>
		protected override void LoadContent()
		{
			RunInExceptionHandlingContext(() => UIStyle.LoadContent());

			base.LoadContent();
		}

		/// <summary>
		/// UnloadContent will be called once per game and is the place to unload
		/// all content.
		/// </summary>
		protected override void UnloadContent()
		{
			// TODO: Unload any non ContentManager content here
		}

		/// <summary>
		/// Updates the specified game time.
		/// </summary>
		/// <param name="gameTime">The game time.</param>
		protected override void Update(GameTime gameTime)
		{
			MainScreen.HandleInput();

			base.Update(gameTime);
		}

		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">The game time.</param>
		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.Black);
			MainScreen.Draw();

			base.Draw(gameTime);
		}

		/// <summary>
		/// Sets the content of the main screen.
		/// </summary>
		/// <param name="widget">The widget.</param>
		public void SetMainScreenContent(NuclearUI.Widget widget)
		{
			MainScreen.Root.Clear();
			MainScreen.Root.AddChild(widget);
			MainScreen.Resize(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
		}

		/// <summary>
		/// Closes the combatlog.
		/// </summary>
		public void CloseCombatlog()
		{
			if (CombatLogStatistics != null)
			{
				CombatLogStatistics.Dispose();
				CombatLogStatistics = null;
			}

			if (combatLogParser == null)
				return;

			combatLogParser.CombatLogEntriesChanged -= OnCombatLogChanged;
			combatLogParser.ExceptionOccured -= OnException;
			combatLogParser.Dispose();
			combatLogParser = null;
		}

		/// <summary>
		/// Initializes the combatlog.
		/// </summary>
		/// <param name="combatLogPath">The combat log path.</param>
		public void InitializeCombatlog(string combatLogPath)
		{
			RunInExceptionHandlingContext(
				() =>
					{
						try
						{
							combatLogParser = new CombatLogParser(combatLogPath);
						}
						catch (FileNotFoundException)
						{
							return;
						}

						CombatLogStatistics = new CombatLogStatistics(combatLogParser);
						combatLogParser.BeginParse();
						combatLogParser.CombatLogEntriesChanged += OnCombatLogChanged;
						combatLogParser.ExceptionOccured += OnException;
					});
		}

		/// <summary>
		/// Runs the given action inside an exception handling context.
		/// </summary>
		/// <param name="action">The action.</param>
		public void RunInExceptionHandlingContext(Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				OnException(this, new ThreadExceptionEventArgs(ex));
			}
		}

		/// <summary>
		/// Called when [combat log changed].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="EventArgs" /> instance containing the event data.</param>
		private void OnCombatLogChanged(object sender, EventArgs args)
		{
			CombatLogChanged.InvokeNullSafe(this);
		}

		/// <summary>
		/// Called when an exception if encountered.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="ThreadExceptionEventArgs"/> instance containing the event data.</param>
		private void OnException(object sender, ThreadExceptionEventArgs args)
		{
			var exception =
				(args.Exception is AggregateException)
					? (args.Exception as AggregateException).Flatten().InnerException
					: args.Exception;

			GameStateManager.DisplayException(exception.ToString());
		}
	}
}
